﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace HGPFramework
{
    public class EasyEncoding
    {
        public const string INIT_VECTOR_ENCRYPT_DECRYPT = "hgpframework1234"; // 16 char hgpframework1234

        public static string Encrypt(string plainText, string pass)
        {
            string initVector = INIT_VECTOR_ENCRYPT_DECRYPT;

            var rijndaelKey =
                new RijndaelEnhanced(pass, initVector);

            return rijndaelKey.Encrypt(plainText);
        }

        public static string Descrypt(string cipherText, string pass)
        {
            string initVector = INIT_VECTOR_ENCRYPT_DECRYPT;
            var re = new RijndaelEnhanced(pass, initVector);
            return re.Decrypt(cipherText);
        }

        public static bool EncryptFile(string pathFile, string pass)
        {
            string initVector = INIT_VECTOR_ENCRYPT_DECRYPT;

            var rijndaelKey =
                new RijndaelEnhanced(pass, initVector);

            if (!File.Exists(pathFile))
            {
                return false;
            }
            try
            {
                byte[] content = File.ReadAllBytes(pathFile);
                byte[] EncryptContent = rijndaelKey.EncryptToBytes(content);
                File.WriteAllBytes(pathFile, EncryptContent);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                return false;
            }

            return true;
        }

        public static bool DescryptFile(string pathFile, string pass, out MemoryStream stream)
        {
            string initVector = INIT_VECTOR_ENCRYPT_DECRYPT;
            var re = new RijndaelEnhanced(pass, initVector);

            if (!File.Exists(pathFile))
            {
                stream = null;
                return false;
            }
            try
            {
                byte[] content = File.ReadAllBytes(pathFile);
                byte[] DecryptContent = re.DecryptToBytes(content);

                stream = new MemoryStream(DecryptContent);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                stream = null;
                return false;
            }

            return true;
        }

        public class SHA1MD5
        {
            public static string SHA1Encoding(string Data)
            {
                var sp = new SHA1CryptoServiceProvider();
                byte[] bdrr = Encoding.UTF8.GetBytes(Data);
                byte[] barr = sp.ComputeHash(bdrr);
                return Convert.ToBase64String(barr);
            }

            public static string MD5Encoding(string Data)
            {
                var md5 = new MD5CryptoServiceProvider();
                byte[] bdrr = Encoding.UTF8.GetBytes(Data);
                byte[] barr = md5.ComputeHash(bdrr);
                return Convert.ToBase64String(barr);
            }
        }
    }

    public class RijndaelEnhanced
    {
        private static string DEFAULT_HASH_ALGORITHM = "SHA1";

        private static int DEFAULT_KEY_SIZE = 256;

        private static int MAX_ALLOWED_SALT_LEN = 255;

        private static int MIN_ALLOWED_SALT_LEN = 4;

        private static readonly int DEFAULT_MIN_SALT_LEN = MIN_ALLOWED_SALT_LEN;
        private static int DEFAULT_MAX_SALT_LEN = 8;

        private readonly ICryptoTransform decryptor;
        private readonly ICryptoTransform encryptor;
        private readonly int maxSaltLen = -1;
        private readonly int minSaltLen = -1;

        public RijndaelEnhanced(string passPhrase)
            :
                this(passPhrase, null)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector)
            :
                this(passPhrase, initVector, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen)
            :
                this(passPhrase, initVector, minSaltLen, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen,
            int maxSaltLen)
            :
                this(passPhrase, initVector, minSaltLen, maxSaltLen, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen,
            int maxSaltLen,
            int keySize)
            :
                this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, null)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen,
            int maxSaltLen,
            int keySize,
            string hashAlgorithm)
            :
                this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize,
                    hashAlgorithm, null)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen,
            int maxSaltLen,
            int keySize,
            string hashAlgorithm,
            string saltValue)
            :
                this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize,
                    hashAlgorithm, saltValue, 1)
        {
        }

        public RijndaelEnhanced(string passPhrase,
            string initVector,
            int minSaltLen,
            int maxSaltLen,
            int keySize,
            string hashAlgorithm,
            string saltValue,
            int passwordIterations)
        {
            if (minSaltLen < MIN_ALLOWED_SALT_LEN)
            {
                this.minSaltLen = DEFAULT_MIN_SALT_LEN;
            }
            else
            {
                this.minSaltLen = minSaltLen;
            }
            if (maxSaltLen < 0 || maxSaltLen > MAX_ALLOWED_SALT_LEN)
            {
                this.maxSaltLen = DEFAULT_MAX_SALT_LEN;
            }
            else
            {
                this.maxSaltLen = maxSaltLen;
            }
            if (keySize <= 0)
            {
                keySize = DEFAULT_KEY_SIZE;
            }
            if (hashAlgorithm == null)
            {
                hashAlgorithm = DEFAULT_HASH_ALGORITHM;
            }
            else
            {
                hashAlgorithm = hashAlgorithm.ToUpper().Replace("-", string.Empty);
            }
            var initVectorBytes = (byte[]) null;

            var saltValueBytes = (byte[]) null;

            if (initVector == null)
            {
                initVectorBytes = new byte[0];
            }
            else
            {
                initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            }
            if (saltValue == null)
            {
                saltValueBytes = new byte[0];
            }
            else
            {
                saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
            }
            var password = new PasswordDeriveBytes(
                passPhrase,
                saltValueBytes,
                hashAlgorithm,
                passwordIterations);

            byte[] keyBytes = password.GetBytes(keySize/8);

            var symmetricKey = new RijndaelManaged();

            if (initVectorBytes.Length == 0)
            {
                symmetricKey.Mode = CipherMode.ECB;
            }
            else
            {
                symmetricKey.Mode = CipherMode.CBC;
            }
            encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
        }

        /// <summary>
        ///     Encrypts a string value generating a base64-encoded string.
        /// </summary>
        /// <param name="plainText">
        ///     Plain text string to be encrypted.
        /// </param>
        /// <returns>
        ///     Cipher text formatted as a base64-encoded string.
        /// </returns>
        public string Encrypt(string plainText)
        {
            return Encrypt(Encoding.UTF8.GetBytes(plainText));
        }

        /// <summary>
        ///     Encrypts a byte array generating a base64-encoded string.
        /// </summary>
        /// <param name="plainTextBytes">
        ///     Plain text bytes to be encrypted.
        /// </param>
        /// <returns>
        ///     Cipher text formatted as a base64-encoded string.
        /// </returns>
        public string Encrypt(byte[] plainTextBytes)
        {
            return Convert.ToBase64String(EncryptToBytes(plainTextBytes));
        }

        /// <summary>
        ///     Encrypts a string value generating a byte array of cipher text.
        /// </summary>
        /// <param name="plainText">
        ///     Plain text string to be encrypted.
        /// </param>
        /// <returns>
        ///     Cipher text formatted as a byte array.
        /// </returns>
        public byte[] EncryptToBytes(string plainText)
        {
            return EncryptToBytes(Encoding.UTF8.GetBytes(plainText));
        }

        /// <summary>
        ///     Encrypts a byte array generating a byte array of cipher text.
        /// </summary>
        /// <param name="plainTextBytes">
        ///     Plain text bytes to be encrypted.
        /// </param>
        /// <returns>
        ///     Cipher text formatted as a byte array.
        /// </returns>
        public byte[] EncryptToBytes(byte[] plainTextBytes)
        {
            byte[] plainTextBytesWithSalt = AddSalt(plainTextBytes);

            var memoryStream = new MemoryStream();

            lock (this)
            {
                var cryptoStream = new CryptoStream(
                    memoryStream,
                    encryptor,
                    CryptoStreamMode.Write);

                cryptoStream.Write(plainTextBytesWithSalt,
                    0,
                    plainTextBytesWithSalt.Length);

                cryptoStream.FlushFinalBlock();

                byte[] cipherTextBytes = memoryStream.ToArray();

                memoryStream.Close();
                cryptoStream.Close();

                return cipherTextBytes;
            }
        }

        /// <summary>
        ///     Decrypts a base64-encoded cipher text value generating a string result.
        /// </summary>
        /// <param name="cipherText">
        ///     Base64-encoded cipher text string to be decrypted.
        /// </param>
        /// <returns>
        ///     Decrypted string value.
        /// </returns>
        public string Decrypt(string cipherText)
        {
            return Decrypt(Convert.FromBase64String(cipherText));
        }

        /// <summary>
        ///     Decrypts a byte array containing cipher text value and generates a
        ///     string result.
        /// </summary>
        /// <param name="cipherTextBytes">
        ///     Byte array containing encrypted data.
        /// </param>
        /// <returns>
        ///     Decrypted string value.
        /// </returns>
        public string Decrypt(byte[] cipherTextBytes)
        {
            byte[] a = DecryptToBytes(cipherTextBytes);

            if (a == null)
            {
                return null;
            }
            return Encoding.UTF8.GetString(a);
        }

        /// <summary>
        ///     Decrypts a base64-encoded cipher text value and generates a byte array
        ///     of plain text data.
        /// </summary>
        /// <param name="cipherText">
        ///     Base64-encoded cipher text string to be decrypted.
        /// </param>
        /// <returns>
        ///     Byte array containing decrypted value.
        /// </returns>
        public byte[] DecryptToBytes(string cipherText)
        {
            return DecryptToBytes(Convert.FromBase64String(cipherText));
        }

        /// <summary>
        ///     Decrypts a base64-encoded cipher text value and generates a byte array
        ///     of plain text data.
        /// </summary>
        /// <param name="cipherTextBytes">
        ///     Byte array containing encrypted data.
        /// </param>
        /// <returns>
        ///     Byte array containing decrypted value.
        /// </returns>
        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            var decryptedBytes = (byte[]) null;
            var plainTextBytes = (byte[]) null;
            int decryptedByteCount = 0;
            int saltLen = 0;

            try
            {
                var memoryStream = new MemoryStream(cipherTextBytes);

                decryptedBytes = new byte[cipherTextBytes.Length];

                lock (this)
                {
                    var cryptoStream = new CryptoStream(
                        memoryStream,
                        decryptor,
                        CryptoStreamMode.Read);

                    decryptedByteCount = cryptoStream.Read(decryptedBytes,
                        0,
                        decryptedBytes.Length);
                    memoryStream.Close();
                    cryptoStream.Close();
                }

                if (maxSaltLen > 0 && maxSaltLen >= minSaltLen)
                {
                    saltLen = (decryptedBytes[0] & 0x03) |
                              (decryptedBytes[1] & 0x0c) |
                              (decryptedBytes[2] & 0x30) |
                              (decryptedBytes[3] & 0xc0);
                }

                plainTextBytes = new byte[decryptedByteCount - saltLen];

                Array.Copy(decryptedBytes, saltLen, plainTextBytes,
                    0, decryptedByteCount - saltLen);

                return plainTextBytes;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        ///     Adds an array of randomly generated bytes at the beginning of the
        ///     array holding original plain text value.
        /// </summary>
        /// <param name="plainTextBytes">
        ///     Byte array containing original plain text value.
        /// </param>
        /// <returns>
        ///     Either original array of plain text bytes (if salt is not used) or a
        ///     modified array containing a randomly generated salt added at the
        ///     beginning of the plain text bytes.
        /// </returns>
        private byte[] AddSalt(byte[] plainTextBytes)
        {
            if (maxSaltLen == 0 || maxSaltLen < minSaltLen)
            {
                return plainTextBytes;
            }
            byte[] saltBytes = GenerateSalt();

            var plainTextBytesWithSalt = new byte[plainTextBytes.Length +
                                                  saltBytes.Length];
            Array.Copy(saltBytes, plainTextBytesWithSalt, saltBytes.Length);

            Array.Copy(plainTextBytes, 0,
                plainTextBytesWithSalt, saltBytes.Length,
                plainTextBytes.Length);

            return plainTextBytesWithSalt;
        }

        /// <summary>
        ///     Generates an array holding cryptographically strong bytes.
        /// </summary>
        /// <returns>
        ///     Array of randomly generated bytes.
        /// </returns>
        /// <remarks>
        ///     Salt size will be defined at random or exactly as specified by the
        ///     minSlatLen and maxSaltLen parameters passed to the object constructor.
        ///     The first four bytes of the salt array will contain the salt length
        ///     split into four two-bit pieces.
        /// </remarks>
        private byte[] GenerateSalt()
        {
            int saltLen = 0;

            if (minSaltLen == maxSaltLen)
            {
                saltLen = minSaltLen;
            }
            else
            {
                saltLen = GenerateRandomNumber(minSaltLen, maxSaltLen);
            }
            var salt = new byte[saltLen];

            var rng = new RNGCryptoServiceProvider();

            rng.GetNonZeroBytes(salt);

            salt[0] = (byte) ((salt[0] & 0xfc) | (saltLen & 0x03));
            salt[1] = (byte) ((salt[1] & 0xf3) | (saltLen & 0x0c));
            salt[2] = (byte) ((salt[2] & 0xcf) | (saltLen & 0x30));
            salt[3] = (byte) ((salt[3] & 0x3f) | (saltLen & 0xc0));

            return salt;
        }

        /// <summary>
        ///     Generates random integer.
        /// </summary>
        /// <param name="minValue">
        ///     Min value (inclusive).
        /// </param>
        /// <param name="maxValue">
        ///     Max value (inclusive).
        /// </param>
        /// <returns>
        ///     Random integer value between the min and max values (inclusive).
        /// </returns>
        /// <remarks>
        ///     This methods overcomes the limitations of .NET Framework's Random
        ///     class, which - when initialized multiple times within a very short
        ///     period of time - can generate the same "random" number.
        /// </remarks>
        private int GenerateRandomNumber(int minValue, int maxValue)
        {
            var randomBytes = new byte[4];

            var rng = new RNGCryptoServiceProvider();
            rng.GetBytes(randomBytes);

            int seed = ((randomBytes[0] & 0x7f) << 24) |
                       (randomBytes[1] << 16) |
                       (randomBytes[2] << 8) |
                       (randomBytes[3]);

            var random = new Random(seed);

            return random.Next(minValue, maxValue + 1);
        }
    }
}